/*
===========================================================================
This source file is part of DyLab (Dynamic Laboratory)
For the latest info, see http://dylab.googlecode.com

Copyright (c) 2006-2008 Lukas Krejci
(krejci.lukas@volny.cz)

This file is part of DyLab.

    DyLab is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    DyLab is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with DyLab.  If not, see <http://www.gnu.org/licenses/>.
===========================================================================
*/

//===========================================================================================
#include <dylab/utils/geometry/EdgeGeometry3.h>

//===========================================================================================
bool dylab::EdgeGeometry3::computeParameter(const Vector3 & _p, real_t & _t, real_t _paramTolerance) const
{                
    // compute some edges
    Vector3 pv0 = _p - mVertices[V_0];
    Vector3 v1v0 = mVertices[V_1] - mVertices[V_0];

    // compute parametr _t
    if (v1v0.x() != Math::ZERO)                    
        _t = pv0.x() / v1v0.x();                    
    else if (v1v0.y() != Math::ZERO)                    
        _t = pv0.y() / v1v0.y();
    else if (v1v0.z() != Math::ZERO)
        _t = pv0.z() / v1v0.z();
    else
		throw ENumericError(String::fmt("Can't compute barycentric coordinates for point %1% inside the edge %2% (degenerated: %3%)", _p, *this, isDegenerated()));

    // test, if parametr _t match for all coordinates (we must allow greater tollerance)
    if ((v1v0 * _t).almostEqual(pv0, _paramTolerance))
        return true;
    else                    
        return false;
}
//-----------------------------------------------------------------------------
dylab::EdgeGeometry3::pointcls_t 
dylab::EdgeGeometry3::classifyPoint(const Vector3 & _p, real_t _paramTolerance, real_t _borderTolerance) const
{
    // compute parameter of the point _p inside the edge
    real_t t;                    
    if (!computeParameter(_p, t, _paramTolerance)) // the point is outside the edge line (i.e., it has no parameter)
        return pointcls_t(Math::POINTCLS_OUTER, (vertexidx_t)-1);

    // store the zero and one flag of the point parameter
    bool tAlmostZero = _p.almostEqual(mVertices[V_0], _borderTolerance) || Math::almostZero(t, _paramTolerance);
    bool tAlmostOne = _p.almostEqual(mVertices[V_1], _borderTolerance) || Math::almostOne(t, _paramTolerance);

    // first test, whether the point is not outside
    if (((t > Math::ZERO) || tAlmostZero) &&  ((t < Math::ONE) || tAlmostOne))
    {
        // test, if point is one of edge's vertices
        if (tAlmostZero)                                
            return pointcls_t(Math::POINTCLS_VERTEX, V_0);
        if (tAlmostOne)
            return pointcls_t(Math::POINTCLS_VERTEX, V_1);

        // _p is inner point
        return pointcls_t(Math::POINTCLS_INNER, (vertexidx_t)-1);
    }
    else
    {
        // the point is outside
        return pointcls_t(Math::POINTCLS_OUTER, (vertexidx_t)-1);
    }
}
//-----------------------------------------------------------------------------

//===========================================================================================    
std::ostream & dylab::operator << (std::ostream & _os, const EdgeGeometry3::pointcls_t & _pointCls)
{ 
    switch (_pointCls.first)
    {
    case Math::POINTCLS_INNER: 
        return _os << "POINTCLS_INNER"; 
    case Math::POINTCLS_VERTEX: 
		return _os << String::fmt("POINTCLS_VERTEX(V_%1%)", _pointCls.second); 
    case Math::POINTCLS_OUTER: 
        return _os << "POINTCLS_OUTER"; 
    default: 
		throw EInvalidArgument(String::fmt("Invalid point-edge classification: %1%", _pointCls.first));
        return _os;
    }
}
//-----------------------------------------------------------------------------